﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace DynaSpace.Entity
{
	/// <summary>
	/// Reprezentuje kolekci <see cref="EntityBase">entit řazenou do sektorů podle pozic pro rychlejší procházení.</see>.
	/// </summary>
	public class EntityCollection
	{
		/// <summary>
		/// Šířka jednoho sektoru v polích
		/// </summary>
		internal const int SECTORSIZE = 20;

		/// <summary>
		/// Seznam sektorů
		/// </summary>
		private LinkedList<EntityBase>[,] sectors;

		private int width, height;
		private int widthSectors, heightSectors;
		
		/// <summary>
		/// Seznam entit, které se mají updatovat
		/// </summary>
		internal LinkedList<UpdatableEntity> updatableEntities;

		/// <summary>
		/// Seznam entit, které se mají při dalším updatu přidat do updatableEntities
		/// Je to kvůli tomu, aby se nemodifikovala běhu jmenovaná kolekce při procházení enumeratorem
		/// </summary>
		internal LinkedList<UpdatableEntity> updatableEntitiesToAdd;

		/// <summary>
		/// Vytvoří <see cref="EntityCollection"/> o danné šířce a výšce.
		/// </summary>
		/// <param name="width">Šířka (v polích)</param>
		/// <param name="height">Výška (v polích)</param>
		public EntityCollection(int width, int height)
		{
			this.width = width;
			this.height = height;
			this.widthSectors = (width / SECTORSIZE) + ((width % SECTORSIZE == 0) ? 0 : 1);
			this.heightSectors = (height / SECTORSIZE) + ((height % SECTORSIZE == 0) ? 0 : 1);
			sectors = new LinkedList<EntityBase>[this.width, this.height];
			updatableEntities = new LinkedList<UpdatableEntity>();
			updatableEntitiesToAdd = new LinkedList<UpdatableEntity>();
		}

		/// <summary>
		/// Vrací šířku v polích.
		/// </summary>
		public int Width
		{
			get
			{
				return width;
			}
		}

		/// <summary>
		/// Vrací výšku v polích.
		/// </summary>
		public int Height
		{
			get
			{
				return height;
			}
		}

		/// <summary>
		/// Přidá entitu do kolekce do příslušného sektoru
		/// </summary>
		/// <param name="entity">Entita k přidání</param>
		public void Add(EntityBase entity)
		{
			// pokud má být i v kolekci pro update v každé iteraci, tak tam entitu přidáme
			if (entity is UpdatableEntity)
			{
				(entity as UpdatableEntity).isDeleted = false;

				if (!updatableEntities.Contains(entity as UpdatableEntity))
				{
					updatableEntitiesToAdd.AddLast(entity as UpdatableEntity);
				}
			}

			// najdeme sektor, kam patří a přídáme jej
			int sectorX = (int)entity.Position.X / SECTORSIZE;
			int sectorY = (int)entity.Position.Y / SECTORSIZE;

			if (sectors[sectorX, sectorY] == null)
			{
				sectors[sectorX, sectorY] = new LinkedList<EntityBase>();
			}

			sectors[sectorX,sectorY].AddLast(entity);
		}

		/// <summary>
		/// Odebere entitu z kolekce z příslušného sektoru
		/// </summary>
		/// <param name="entity">Entita k odebrání</param>
		public void Remove(EntityBase entity)
		{
			int sectorX = (int)entity.Position.X / SECTORSIZE;
			int sectorY = (int)entity.Position.Y / SECTORSIZE;

			if (sectors[sectorX, sectorY] != null)
			{
				sectors[sectorX, sectorY].Remove(entity);
			}

			// připravíme ke smazání
			if (entity is UpdatableEntity)
			{
				(entity as UpdatableEntity).isDeleted = true;
			}
		}

		/// <summary>
		/// Vrací seznam všech <see cref="EntityBase">entit</see>, které se nacházejí v předaném výřezu 
		/// </summary>
		/// <param name="left">Levý roh výřezu</param>
		/// <param name="top">Horní roh výřezu</param>
		/// <param name="width">Šířka výřezu</param>
		/// <param name="height">Výška výřezu</param>
		/// <returns>Vrací seznam odpovídajících <see cref="EntityBase">entit</see></returns>
		public IEnumerable<EntityBase> GetCloseEntities(float left, float top, float width, float height)
		{
			int leftSector = (int)((left) / SECTORSIZE);
			int topSector = (int)((top) / SECTORSIZE);

			int rightSector = (int)((left + width) / SECTORSIZE);
			int bottomSector = (int)((top + height) / SECTORSIZE);

			// omezit levou a horni hranici
			leftSector = leftSector < 0 ? 0 : leftSector;
			topSector = topSector < 0 ? 0 : topSector;
			rightSector = rightSector < 0 ? 0 : rightSector;
			bottomSector = bottomSector < 0 ? 0 : bottomSector;

			// omezit pravou a spodni hranici
			leftSector = leftSector >= widthSectors ? widthSectors - 1 : leftSector;
			topSector = topSector >= heightSectors ? heightSectors - 1 : topSector;
			rightSector = rightSector >= widthSectors ? widthSectors - 1 : rightSector;
			bottomSector = bottomSector >= heightSectors ? heightSectors - 1 : bottomSector;

			// projít všechny sektory v oblasti zájmu
			for (int x = leftSector; x <= rightSector; x++)
			{
				for (int y = topSector; y <= bottomSector; y++)
				{
					// procházet jen inicializované sektory
					if (sectors[x, y] != null)
					{
						// projít všechny entity v sektoru
						foreach (EntityBase entity in sectors[x, y])
						{
							// vybrat ty, které spadají do výběru
							if (entity.Position.IsEntityInRange(left, top, width, height))
							{
								yield return entity;
							}
						}
					}
				}
			}
		}

		/// <summary>
		/// Kontroluje, zda některý objekt z kolekce entit nekoliduje s předanou <paramref name="entity">entitou</paramref> na teoretické <paramref name="position">pozici</paramref>.
		/// </summary>
		/// <param name="entity"><see cref="EntityBase"/>, která se kontroluje kvůli případné kolizi.</param>
		/// <param name="position">Teoretická pozice <paramref name="entity">entity</paramref>, pro kterou se zjišťujě kolize.</param>
		/// <returns>Vrací, zda se vyskytla kolize.</returns>
		public bool CheckCollision(EntityBase entity, Vector2 position)
		{
			if(position.IsOutOfRangeEntity(this, entity.Size))
				return true;

			foreach (var item in GetCloseEntities(position.X,position.Y,position.X + 1,position.Y + 1))
			{
				if (item.IsCollision(entity, position))
					return true;
			}
			return false;
		}

		/// <summary>
		/// Promaže všechny sektory
		/// </summary>
		public void Clear()
		{
			// smazat ostatni pomocne kolekce
			updatableEntities.Clear();
			updatableEntitiesToAdd.Clear();

			// smazat sektory
			for (int i = 0; i < widthSectors ; i++)
			{
				for (int j = 0; j < heightSectors; j++)
				{
					sectors[i, j] = null;
				}
			}
		}

		/// <summary>
		/// Provede update entity odvozených z <see cref="UpdatableEntity"/>
		/// </summary>
		/// <param name="gameTime"></param>
		internal void Update(GameTime gameTime)
		{
			LinkedList<UpdatableEntity> entitiesToDelete = null;
			foreach (UpdatableEntity entity in updatableEntities)
			{
				entity.Update(gameTime);

				if (entity.isDeleted) // byla při updatu smazána?
				{
					// pokud ano, tak zkontrolujeme, jestli existuje kolekce smazanych polozek
					if (entitiesToDelete == null)
						entitiesToDelete = new LinkedList<UpdatableEntity>();

					// pridame do ni nove smazanou entitu
					entitiesToDelete.AddLast(entity);
				}
					
			}

			if (entitiesToDelete != null) // pokud se ma neco odebirat, vse projdeme a smazameme
			{
				foreach (UpdatableEntity entity in entitiesToDelete)
				{
					updatableEntities.Remove(entity);
				}
			}

			if (updatableEntitiesToAdd.First != null) // existuji nove entity na pridani
			{
				foreach (UpdatableEntity entity in updatableEntitiesToAdd)
				{
					updatableEntities.AddLast(entity);
				}

				// smazat, entity jsou již přidané v hlavní kolekci updatableEntitiesToAdd
				updatableEntitiesToAdd.Clear();
			}
		}
	}
}
